package com.spring.springboot.sys.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.spring.springboot.entity.User;
import com.spring.springboot.mapper.UserMapper;
import com.spring.springboot.sys.base.ServiceException;
import com.spring.springboot.sys.core.config.SecurityConfigProperties;
import com.spring.springboot.sys.core.constant.BusinessConstant;
import com.spring.springboot.sys.core.constant.FieldConstants;
import com.spring.springboot.sys.core.constant.RedisConstants;
import com.spring.springboot.sys.core.enums.ResponseCodeEnum;
import com.spring.springboot.sys.core.model.*;
import com.spring.springboot.sys.jwt.JwtTokenUtil;
import com.spring.springboot.sys.util.BeanUtils;
import com.spring.springboot.sys.util.RedisUtils;
import com.spring.springboot.sys.util.SendMessageUtil;
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Objects;

/**
 * @author 15625
 * @date 2021-04-20 15:59
 * @description 系统登录实现
 */
@Slf4j
@Api(tags = "系统登录")
@RestController
@RequestMapping("/api/system")
public class LoginController {

    @Resource
    private UserMapper userMapper;

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private DefaultKaptcha defaultKaptcha;

    @Resource
    private SecurityConfigProperties securityConfigProperties;

    @PostMapping("/loginByPassword")
    @ApiOperation(value = "账号密码登录", notes = "账号密码登录")
    @ApiImplicitParam(name = "loginByPassword", value = "登陆参数", required = true, paramType = "body", dataType = "PasswordLoginParam", dataTypeClass = PasswordLoginParam.class)
    public Result loginByPassword(@RequestBody PasswordLoginParam params) {
        try {
            String account = params.getAccount();
            String password = params.getPassword();
            if (StringUtils.isBlank(account) || StringUtils.isBlank(password)) {
                throw new ServiceException(ResponseCodeEnum.LOGIN_PARAM_MISSING);
            }
            //校验用户名和密码
            User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                    .eq(User::getDelFlag, BusinessConstant.DeleteStatus.NO_DELETE)
                    .eq(User::getAccount, account));
            if (Objects.isNull(user)) {
                throw new ServiceException(ResponseCodeEnum.USER_NOT_EXIST);
            }
            //校验密码
            String passwordMd5 = SecureUtil.md5(password);
            if (!Objects.equals(passwordMd5, user.getPassword())) {
                throw new ServiceException(ResponseCodeEnum.WRONG_PASSWORD);
            }
            //登录成功
            UserInfo userInfo = BeanUtils.copyProperties(user, UserInfo.class);
            WebUserDetails userDetails = new WebUserDetails(userInfo);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // 生成token信息并存入redis当中
            String token = jwtTokenUtil.generateToken(userInfo);
            // 向前端返回token信息
            userInfo.setToken(securityConfigProperties.getPrefix() + token);
            log.info("====保存到redis中的token加密前的信息====：{}", userInfo.getToken());
            log.info("====保存到redis中的token加密后的信息====：{}", SecureUtil.md5(userInfo.getToken()));
            // 将token存放到redis中
            redisUtils.set(String.format(RedisConstants.LOGIN_TOKEN_PREFIX, userInfo.getAccount()), SecureUtil.md5(userInfo.getToken()), securityConfigProperties.getExpiration());
            return Result.success("登录成功！", userInfo);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(ResponseCodeEnum.SERVE_ERROR);
        }
    }

    @PostMapping("/loginByPhone")
    @ApiOperation(value = "手机号登录", notes = "手机号登录")
    @ApiImplicitParam(name = "loginByPhone", value = "登陆参数", required = true, paramType = "body", dataType = "PhoneLoginParam", dataTypeClass = PhoneLoginParam.class)
    public Result loginByPhone(@RequestBody PhoneLoginParam params) {
        try {
            String phone = params.getPhone();
            String msgCode = params.getMsgCode();
            if (StringUtils.isBlank(phone) || StringUtils.isBlank(msgCode)) {
                throw new ServiceException(ResponseCodeEnum.LOGIN_PARAM_MISSING);
            }
            //校验短信验证码
            String redisMsgCode = (String) redisUtils.get(String.format(RedisConstants.LOGIN_PHONE_MSG_PREFIX, phone));
            if (!Objects.equals(msgCode, redisMsgCode)) {
                throw new ServiceException(ResponseCodeEnum.VERIFICATION_CODE_ERROR);
            }
            //校验用户是否存在
            User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                    .eq(User::getDelFlag, BusinessConstant.DeleteStatus.NO_DELETE)
                    .eq(User::getPhone, phone));
            if (Objects.isNull(user)) {
                //账号不存在
                throw new ServiceException(ResponseCodeEnum.USER_NOT_EXIST);
            }
            //登录成功
            UserInfo userInfo = BeanUtils.copyProperties(user, UserInfo.class);
            WebUserDetails userDetails = new WebUserDetails(userInfo);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // 生成token信息并存入redis当中
            String token = jwtTokenUtil.generateToken(userInfo);
            // 向前端返回token信息
            userInfo.setToken(securityConfigProperties.getPrefix() + token);
            log.info("====保存到redis中的token加密前的信息====：{}", userInfo.getToken());
            log.info("====保存到redis中的token加密后的信息====：{}", SecureUtil.md5(userInfo.getToken()));
            // 将token存放到redis中
            redisUtils.set(String.format(RedisConstants.LOGIN_TOKEN_PREFIX, userInfo.getAccount()), SecureUtil.md5(userInfo.getToken()), securityConfigProperties.getExpiration());
            return Result.success("登录成功！", userInfo);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(500, "系统异常！");
        }
    }

    @ApiOperation("手机号发送短信")
    @PostMapping("/sendMessage")
    @ApiImplicitParam(name = "phone", value = "手机号", required = true, paramType = "query", dataType = "String", dataTypeClass = String.class)
    public Result sendMessage(@ApiIgnore String phone) {
        phone = FieldConstants.PHONE_PREFIX.concat(phone);
        //支持发送多个手机号
        String[] sendPhone = {phone};
        SendSmsResponse sendSmsResponse = SendMessageUtil.sendMessage(sendPhone);
        //转换成json
        JSONObject responseJson = JSONObject.parseObject(SendSmsResponse.toJsonString(sendSmsResponse));
        //判断验证码是否发送成功
        JSONObject sendStatusSet = responseJson.getJSONObject(FieldConstants.SEND_STATUS_SET);
        String code = sendStatusSet.getString(FieldConstants.CODE);
        if (!FieldConstants.OK.equals(code)) {
            throw new ServiceException(500, sendStatusSet.getString(FieldConstants.MESSAGE));
        }
        log.info("短信发送返回信息：{}", SendSmsResponse.toJsonString(sendSmsResponse));
        return Result.success("短信发送成功！请注意查收！");
    }

    /**
     * 普通验证码
     *
     * @param httpServletRequest  HttpServletRequest
     * @param httpServletResponse HttpServletResponse
     * @throws Exception Exception
     */
    @ApiOperation("生成验证码-普通验证码")
    @GetMapping("/createCode")
    public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        //字节数组输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            // 生产验证码字符串并保存到session中，并未登陆账号、不好做key值，不适宜放入redis
            String code = defaultKaptcha.createText();
            log.info("-------生成的验证码是-------->{}", code);
            httpServletRequest.getSession().setAttribute("verificationCode", code);

            // 使用生成的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
            BufferedImage image = defaultKaptcha.createImage(code);
            ImageIO.write(image, "jpg", outputStream);

            //图片转换成Base64
            String encode = Base64.getEncoder().encodeToString(outputStream.toByteArray());
            log.info("Base64Code:{}", encode);

            // 定义response输出类型为image/jpeg类型，使用response输出流输出图片的byte数组
            byte[] captchaChallengeAsJpeg = outputStream.toByteArray();
            ServletOutputStream responseOutputStream = getOutputStream(httpServletResponse);
            responseOutputStream.write(captchaChallengeAsJpeg);
            responseOutputStream.flush();
            responseOutputStream.close();

        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            throw new ServiceException(ResponseCodeEnum.SERVE_ERROR);
        }
    }

    /**
     * 线段干扰的验证码
     *
     * @param httpServletRequest  httpServletRequest
     * @param httpServletResponse httpServletResponse
     * @throws IOException IOException
     */
    @ApiOperation("验证码生成- 线段干扰验证码")
    @GetMapping("/createLineCaptcha")
    public void createLineCaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        //定义图形验证码的长、宽、验证码字符数、干扰线宽度
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 20);
        //放入session当中，以备后期做验证码校验
        httpServletRequest.getSession().setAttribute("verificationCode", lineCaptcha.getCode());
        //图形验证码写出，可以写出到文件，也可以写出到流
        ServletOutputStream outputStream = getOutputStream(httpServletResponse);
        lineCaptcha.write(outputStream);
        //关闭输出流
        outputStream.flush();
        outputStream.close();
    }

    /**
     * 圆圈干扰验证码
     *
     * @param httpServletRequest  httpServletRequest
     * @param httpServletResponse httpServletResponse
     * @throws IOException IOException
     */
    @ApiOperation("验证码生成-圆圈干扰验证码")
    @GetMapping("/createCircleCaptcha")
    public void createCircleCaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        //定义图形验证码的长、宽、验证码字符数、干扰线宽度
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
        //放入session当中，以备后期做验证码校验
        httpServletRequest.getSession().setAttribute("verificationCode", circleCaptcha.getCode());
        //图形验证码写出，可以写出到文件，也可以写出到流
        ServletOutputStream outputStream = getOutputStream(httpServletResponse);
        circleCaptcha.write(outputStream);
        //关闭输出流
        outputStream.flush();
        outputStream.close();
    }

    /**
     * 扭曲干扰验证码
     *
     * @param httpServletRequest  httpServletRequest
     * @param httpServletResponse httpServletResponse
     * @throws IOException IOException
     */
    @ApiOperation("验证码生成-扭曲干扰验证码")
    @GetMapping("/createShearCaptcha")
    public void createShearCaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        //定义图形验证码的长、宽、验证码字符数、干扰线宽度
        ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(200, 100, 4, 4);
        //放入session当中，以备后期做验证码校验
        httpServletRequest.getSession().setAttribute("verificationCode", shearCaptcha);
        //图形验证码写出，可以写出到文件，也可以写出到流
        ServletOutputStream outputStream = getOutputStream(httpServletResponse);
        shearCaptcha.write(outputStream);
        //关闭输出流
        outputStream.flush();
        outputStream.close();
    }

    /**
     * @param verificationCode   验证码
     * @param httpServletRequest httpServletRequest
     * @return result
     */
    @ApiOperation(value = "校对验证码")
    @PostMapping("/checkCode")
    public Result checkVerificationCode(@RequestParam(value = "verificationCode") String verificationCode, HttpServletRequest httpServletRequest) {
        String verificationCodeIn = (String) httpServletRequest.getSession().getAttribute("verificationCode");
        httpServletRequest.getSession().removeAttribute("verificationCode");
        if (StringUtils.isEmpty(verificationCodeIn) || !verificationCodeIn.equals(verificationCode)) {
            throw new ServiceException(ResponseCodeEnum.VERIFICATION_CODE_ERROR);
        }
        return Result.success("校验通过！");
    }

    /**
     * 返回一个ServletOutputStream
     */
    public ServletOutputStream getOutputStream(HttpServletResponse httpServletResponse) throws IOException {
        httpServletResponse.setHeader("Cache-Control", "no-store");
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", 0);
        httpServletResponse.setContentType("image/jpeg");
        return httpServletResponse.getOutputStream();
    }

    public static void main(String[] args) {
        String s = SecureUtil.md5("jwtSecretKey");
        System.out.println(s);
    }

}
